Introduction to Cobra
In this lesson, you’ll learn about Cobra, which is the de-facto gold standard for Go command-line programs.
We'll cover the following
Cobra is both a library for processing command-line arguments and organizing your commands and an application that can generate a structured skeleton for your program. It is used by a vast number of the most prominent Go command-line tools out there like kubectl (the Kubernetes CLI) and docker (the Docker CLI).
The Cobra commander#
Cobra is named after the comics villain Cobra commander who is the supreme leader of the Cobra terrorist organization in the G.I Joe comics universe. That’s a great inspiration for a command-line program tool. It also has a nice logo!
The author of Cobra, Steve Francia, works for Google and developed a few other notable Go libraries and programs, in particular Hugo and Viper. Spoiler alert: we will incorporate Viper later into multi-git.
Okay. Let’s see what Cobra is all about.
Why Cobra?#
Cobra is mature, battle-tested, and provides a lot of functionality. It is likely familiar to other Go developers, so if you collaborate with other people, they will feel right at home if your command-line program uses Cobra. Here are just some of the features Cobra brings to the table:
- Commands and nested subcommands
- POSIX-compliant flags (including short and long versions)
- Global, local, and cascading flags
- Easy generation of application skeleton
- Intelligent suggestions for typos
- Optional tight integration with viper for 12-factor apps
There are many more features. You may or may not need all these capabilities, but even if you need just a few of them, it’s nice that Cobra provides it to you out of the box.
Let’s see how Cobra is organized.
Understanding Cobra concepts#
Cobra is based on the concepts of commands, arguments and flags. The basic idea is that a command line should read like a sentence:
<Application Name> <verb> <noun> <adjective>
Verbs correspond to commands, nouns correspond to arguments, and flags correspond to adjectives:
<Application name> <command> <argument> <flag>
The command to install Cobra itself is a great example of this idea:
go get -u github.com/spf13/cobra/cobra
The application name is go, the verb is get, the argument is the Cobra package name github.com/spf13/cobra/cobra and the adjective is -u, which means updated.
However, the mapping breaks pretty quickly with nested commands, multiple arguments and multiple flags. For example, take the basic kubectl command kubectl get pods. This command gets the list of pods in the default namespace and prints some information about them. In this command, the application name is kubectl, the command is nested get pods, and there are no arguments or flags. If you consider it as an English sentence, then pods is the noun. Now, if you want to get a specific pod, you pass an argument to the command: kubectl get pod <pod name>. However, from an English grammar point of view, the noun is pod <pod name>, which is half a nested command and half an argument.
The bottom line is that the analogy may be stretched at times. Take it in the spirit in which it was given, as a general guideline for constructing consistent command-line APIs that are easy to use and understand.
Commands#
Commands is the foundational concept that Cobra is built around. Commands may have subcommands, and each level of command may have an action associated with it. Our job as application developers is to design the set of commands and subcommands as the command-line interface of our program.
Cobra helps by providing a command type with lots of functionality, including tens of different methods. We will explore many of these methods in the next lesson.
Commands and subcommands should appear in the correct order in the command-line and before any argument. Flags may be sprinkled anywhere.
command --flag1 sub-command --flag2 arg1 arg2 --flag3
Arguments#
Arguments are typically what the command operates on. It can be filenames, URLs, or anything else. Commands can accept zero or more arguments. Some commands can get an arbitrary number of arguments and operate on all of them. For example, the cat command accepts any number of filenames, concatenates them together, and prints them to standard output:
$ cat file-1 file-2 ... file-N
Cobra distinguishes between positional arguments, which must appear in a certain position, and custom arguments. Cobra provides several ways to specify and validate that the correct number of arguments were passed to your command. You can require the following:
| Validation method | Explanation |
|---|---|
| NoArgs | The command doesn’t accept any arguments. |
| ArbitraryArgs | The command accepts any argument, and there are no restrictions on number or type. |
| OnlyValidArgs | The command requires all positional args to be in the ValidArgs field of Command. |
| MinimumNArgs(int N) | The command will require at least N positional args. |
| MaximumNArgs(int N) | The command will allow up to N positional args. |
| ExactArgs(int N) | The command requires exactly N positional args. |
| ExactValidArgs(int N) | The command requires exactly N positional args, AND all positional args must be in the ValidArgs field of Command. |
| RangeArgs(min, max) | The command allows the number of args to be in the range [min…max] (inclusive). |
You can also define custom validators for arguments, which are functions that accept all the arguments and can return an error if they violate any custom validation rules. Here is the signature of the custom validation function:
func(cmd *cobra.Command, args []string) error
Flags#
Flags are a way to modify the way a command behaves. Sometimes it is not always clear if something needs to be a command, a flag or an argument. For example, many command-line programs have a version command, but others have a --version flag. The end result is the same and displays the version information, but from a CLI design point of view, it’s best to have a consistent approach to avoid confusing the users.
Git took the high road by defining both a version command and a --version flag:
$ git version
git version 2.25.0
Flags are always optional. The default behavior occurs when a flag is absent. Flags can be bare, meaning they have no additional information. The command behaves one way when they are present and another way when they aren’t. For example, the -p of git sends the output to less for a paginated view.
commit 636ba8edb8b24c79059f8dbc4fb895a8a7cac013 (HEAD -> master, origin/master, origin/HEAD)
Author: the-gigi <the.gigi@gmail.com>
Date: Tue Dec 31 14:09:41 2019 -0800
Sync
commit 8b672d75480e68f4f5973ad7f2777409b186e833
Author: the-gigi <the.gigi@gmail.com>
Date: Tue Dec 31 13:03:43 2019 -0800
check that repo names are not empty
commit 27f6eaadac01f1aa8a90d29768bd023c400d0602
Author: the-gigi <the.gigi@gmail.com>
Date: Tue Dec 31 11:25:51 2019 -0800
removed unused import
...
Flags can have long and short names. For example, the git -p flag is also called --paginate. This is a usability feature where the long names are more readable and appropriate for scripts, but when you type commands at a terminal, you may prefer to use short names.
It is good practice to provide both short and long names for flags.
Some flags require additional information. For example, --git-dir=<path> sets the path to the repository the git command should operate on. The default is the current working directory
Now that you understand the basic concepts of Cobra, we’ll discuss how to use it as a library in our programs.
Debugging With Tests
Comparing Cobra to Alternatives